package com.anlogic.sdk.utils.loggers;

import java.io.IOException;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.message.Message;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.console.IOConsole;
import org.eclipse.ui.console.IOConsoleOutputStream;
import com.anlogic.sdk.utils.EnvUtils;

@Plugin(name = "LoggerConsoleAppender", category = "Core", elementType = "appender", printObject = true)
public class LoggerConsoleAppender extends AbstractAppender {
	private static Logger logger;

	private Map<Level, IOConsoleOutputStream> logStreams = new HashMap<>();

	private IOConsoleOutputStream defaultStream;

	private Color traceColor;

	private Color infoColor;

	private Color warnColor;

	private Color errorColor;

	private Color debugColor;

	private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

	private static IOConsole logConsole;

	private boolean initialized = false;

	private List<String> logList;

	private Job logJob;
	
	protected LoggerConsoleAppender(String name, Filter filter, Layout<? extends Serializable> layout) {
		super(name, filter, layout, true, null);
		LoggerContext context = (LoggerContext) LogManager.getContext(false);
		logger = context.getLogger("LogToIDEConsole");
		
		if (!this.initialized) {
			Display.getDefault().asyncExec(new Runnable() {
				public void run() {
					LoggerConsoleAppender.this.initializeOutputStreams();
				}
			});
		}
		
		this.logList = Collections.synchronizedList(new ArrayList<>());
		this.logJob = new Job("Processing log events.") {
			protected IStatus run(IProgressMonitor monitor) {
				List<String> processed;
				synchronized (LoggerConsoleAppender.this.logList) {
					processed = new ArrayList<>(LoggerConsoleAppender.this.logList);
					LoggerConsoleAppender.this.logList.clear();
				}
				for (String logEvent : processed)
					LoggerConsoleAppender.this.logEvent(logEvent);
				return Status.OK_STATUS;
			}
		};
	}

	@PluginFactory
	public static LoggerConsoleAppender createAppender(@PluginAttribute("name") String name,
			@PluginElement("Filters") Filter filter, @PluginElement("Layout") Layout<? extends Serializable> layout) {
		Layout<? extends Serializable> patternLayout = layout;
		if (layout == null)
			patternLayout = PatternLayout.createDefaultLayout();
		return new LoggerConsoleAppender(name, filter, patternLayout);
	}

	public static IOConsole getLogConsole() {
		if (logConsole == null)
			logConsole = new IOConsole("Log Console", null);
		return logConsole;
	}

	@Override
	public void append(LogEvent event) {
		if (logger.getLevel().intLevel() < event.getLevel().intLevel()) {
			return;
		}
		
		StringBuilder resultMessage = new StringBuilder();
		String status = event.getLevel().toString();
		Message eventMessage = event.getMessage();
		String message = this.dateFormat.format(new Date()) + " " + status + "\t: " + eventMessage.getFormattedMessage()
				+ "\n";
		resultMessage.append(message);
		Throwable throwableInformation = eventMessage.getThrowable();
		if (throwableInformation != null) {
			StringBuilder errorTrace = new StringBuilder();
			for (StackTraceElement expString : throwableInformation.getStackTrace()) {
				errorTrace.append(expString.toString() + "\n");
			}
			errorTrace.append(eventMessage + "\n");
			if (!errorTrace.toString().isEmpty()) {
				resultMessage.append("\n");
				resultMessage.append(errorTrace);
			}
		}
		String logString = resultMessage.toString();
		if (!this.logList.contains(logString))
			this.logList.add(logString);
		if (!this.logStreams.isEmpty())
			this.logJob.schedule();
	}

	private void initializeOutputStreams() {
		if (this.initialized)
			return;
		this.initialized = true;
		getLogConsole();
		this.initColor();
		IOConsoleOutputStream traceStream = logConsole.newOutputStream();
		traceStream.setColor(this.traceColor);
		this.logStreams.put(Level.TRACE, traceStream);
		IOConsoleOutputStream debugStream = logConsole.newOutputStream();
		debugStream.setColor(this.debugColor);
		this.logStreams.put(Level.DEBUG, debugStream);
		IOConsoleOutputStream infoStream = logConsole.newOutputStream();
		infoStream.setColor(this.infoColor);
		this.logStreams.put(Level.INFO, infoStream);
		IOConsoleOutputStream warnStream = logConsole.newOutputStream();
		warnStream.setColor(this.warnColor);
		this.logStreams.put(Level.WARN, warnStream);
		IOConsoleOutputStream errorStream = logConsole.newOutputStream();
		errorStream.setColor(this.errorColor);
		this.logStreams.put(Level.ERROR, errorStream);
		this.defaultStream = infoStream;
	}

	private void logEvent(String message) {
		try {
			IOConsoleOutputStream consoleOutputStream = this.defaultStream;
			if (message.contains("DEBUG")) {
				consoleOutputStream = this.logStreams.get(Level.DEBUG);
			} else if (message.contains("INFO")) {
				consoleOutputStream = this.logStreams.get(Level.INFO);
			} else if (message.contains("TRACE")) {
				consoleOutputStream = this.logStreams.get(Level.TRACE);
			} else if (message.contains("ERROR")) {
				consoleOutputStream = this.logStreams.get(Level.ERROR);
			} else if (message.contains("WARN")) {
				consoleOutputStream = this.logStreams.get(Level.WARN);
			}
			if (consoleOutputStream == null)
				consoleOutputStream = this.defaultStream;
			consoleOutputStream.write(message);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void initColor() {
		if (EnvUtils.isDarkTheme()) {
			this.traceColor = new Color(null, 153, 153, 153);
			this.infoColor = new Color(null, 102, 102, 255);
			this.warnColor = new Color(null, 255, 153, 51);
			this.errorColor = new Color(null, 255, 0, 0);
			this.debugColor = new Color(null, 0, 139, 139);
		} else {
			this.traceColor = new Color(null, 102, 102, 102);
			this.infoColor = new Color(null, 51, 51, 170);
			this.warnColor = new Color(null, 204, 102, 0);
			this.errorColor = new Color(null, 204, 0, 0);
			this.debugColor = new Color(null, 0, 139, 139);
		}
	}
}
